package service

import (
	"encoding/json"
	"github.com/go-kit/kit/endpoint"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/net/context"
	"net/http"
)

type Service interface {
	Hash(ctx context.Context, passwd string) (string, error)
	Validate(ctx context.Context, passwd, hash string) (bool, error)
}

type valueService struct{}

func (valueService) Hash(ctx context.Context, passwd string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hash), nil
}

func (valueService) Validate(ctx context.Context, passwd, hash string) (bool, error) {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(passwd))
	if err != nil {
		return false, err
	}
	return true, nil
}
func NewService() Service {
	return valueService{}
}

type hashRequest struct {
	Password string `json:"password"`
}

type hashResponse struct {
	Hash string `json:"hash"`
	Err  string `json:"err,omitempty"`
}

func EncodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
	return json.NewEncoder(w).Encode(response)
}

func DecodeHashRequest(ctx context.Context, r *http.Request) (interface{}, error) {
	var req hashRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		return nil, err
	}
	return req, nil
}

type validateRequest struct {
	Password string `json:"password"`
	Hash     string `json:"hash"`
}

type validateResponse struct {
	Valid bool   `json:"valid"`
	Err   string `json:"err,omitempty"`
}

func DecodeValidateRequest(ctx context.Context, r *http.Request) (interface{}, error) {
	var req validateRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		return nil, err
	}
	return req, nil
}

func MakeHashEndPoint(src Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		req := request.(hashRequest)
		hash, err := src.Hash(ctx, req.Password)
		if err != nil {
			return hashResponse{Hash: hash, Err: err.Error()}, nil
		}
		return hashResponse{hash, ""}, nil
	}
}

func MakeValidateEndPoint(srv Service) endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
		req := request.(validateRequest)
		validate, err := srv.Validate(ctx, req.Password, req.Hash)
		if err != nil {
			return validateResponse{Valid: false, Err: err.Error()}, nil
		}
		return validateResponse{validate, ""}, nil

	}
}

type Endpoints struct {
	HashEndpoint     endpoint.Endpoint
	ValidateEndpoint endpoint.Endpoint
}

func (e Endpoints) Hash(ctx context.Context, passwd string) (string, error) {
	request := hashRequest{Password: passwd}
	hashEndpoint, err := e.HashEndpoint(ctx, request)
	if err != nil {
		return "", err
	}
	return hashEndpoint.(string), nil
}

func (e Endpoints) Validate(ctx context.Context, passwd, hash string) (bool, error) {
	request := validateRequest{Password: passwd, Hash: hash}
	validateEndpoint, err := e.ValidateEndpoint(ctx, request)
	if err != nil {
		return false, err
	}
	return validateEndpoint.(bool), nil
}
